package board;

// DH, 13 MAY 2007, Fixed a bug in negamaxvalue that was
// causing incorrect values to be returned and causing the computer
// player to no longer move its king out of danger.
// Also eliminated the costly tie game on no legal moves check in State.java
// because such a tie does not appear to be allowed by the rules of
// mini chess.

import java.util.Collections;
import java.util.Date;
import java.util.Random;
import java.util.Vector;

public class MainRun {
	static Date turnStart;
	static int nodeCount;
	static int tableMatches;
	static TTable transTable=new TTable(1048576);//2^20
	Random numberGen = new Random();
	static long currentZobrist;
	static TTableEntry tempTTableEntry;
	static int plyDepth;
	
	public static void main (String[] args) {
		
		State boardState = new State();
		Vector MoveList;
		Move currentBestMove=new Move();
		Move tempBestMove=new Move();
		float numCurrentBestMove;//specifies the number of moves with this value found so far.
		
		System.out.println(transTable.getZobristTableString());
		currentZobrist=transTable.initialZobrist+transTable.zobristWhiteOnMove;
		System.out.println("Zobrist for initial board with white on move: "+currentZobrist);
		
		boardState.initializeBoard();
		boardState.drawBoard(System.out);
		while(true){
		  //single level move generator
		  //if its whites turn
		  if(boardState.whitesTurn){
			boardState.saveState();
			turnStart=new Date();
			MoveList=boardState.moveGen();
			boolean plyFinished=true;
			nodeCount=0;
			tableMatches=0;
			plyDepth=3;
			System.out.println("Zobrist before starting plys="+currentZobrist);
			while(plyFinished){
			  plyFinished=false;
			  try{
				System.out.println("Ply Depth: "+ Integer.toString(plyDepth)+".");
		        tempBestMove=new Move();
		        tempBestMove=negaMaxDecision(MoveList,boardState,-2,2);
			    plyDepth++;
			    plyFinished=true;
			    currentBestMove=tempBestMove;
			    System.out.println("Node Count: "+ Integer.toString(nodeCount)+". Table Matches: "+tableMatches+". Best Move has value "+currentBestMove.Value+".");
			    if((currentBestMove.Value==1)||(currentBestMove.Value==-1)){
			    	plyFinished=false;
			    	//this will cause us to break the loop early if it is a known win or loss and make a move.
			    }
			  }
			  catch(Exception err){
				  System.out.println(err.getMessage()); 
			  }
			}
		    boardState.restoreState();
		    System.out.println("Zobrist before move="+currentZobrist);
		    System.out.println("Total Node Count: "+ Integer.toString(nodeCount)+".");
			System.out.println("tempBestMove has value "+tempBestMove.Value+".");
		    System.out.println("Making Best Move. Value "+currentBestMove.Value);
		    System.out.println(currentBestMove.getMoveAsString());
		    boardState.makeMoveRulesEnforced(currentBestMove);
		    updateZobrist(currentBestMove);
		    System.out.println("Zobrist after move="+currentZobrist);
		  
		 
		    //best move based on one run of evaluation function
		    //currentBestMove = selectBestMoveRandom(MoveList);
            //		non-random mover
		    //boardState.makeMove(currentBestMove);
		  
		    //random mover
		    //randomMove(MoveList, boardState);
		  }
		  
		  //if its blacks turn
		  else{
			boolean madeLegalMove=false;
			while(!madeLegalMove){
			  System.out.println("Please enter a move in the form a2-a3:");
			  currentBestMove=readStdInMove(boardState);
			  madeLegalMove=boardState.makeMoveRulesEnforced(currentBestMove);
			}
		  }
		  
		  
		  boardState.drawBoard(System.out);
		  if(boardState.gameOver==true){
			  System.out.println("Game Over.");
			  if(boardState.findPiece('k', new Square())){
				  if(boardState.findPiece('K', new Square())){
				    System.out.println("Game is a tie.");
				  }
				  else System.out.println("black wins.");
			  }
			  else System.out.println("White wins.");
			  break;
		  }
		  System.out.println("-----------------");
		}
	}


	private void randomMove(Vector MoveList, State boardState) {
		//May 27, 2007 moved numberGen creation to beginning of MainRun so the
		//object isn't recreated every time this function is called.
		
		//random mover
		  boardState.makeMove((Move)MoveList.elementAt(numberGen.nextInt(MoveList.size())));
	}


	private static Move selectBestMoveRandom(Vector MoveList) {
		Random numberGen = new Random();
		
		Move currentBestMove;
		float numCurrentBestMove;
		
		currentBestMove=new Move();//ensure currentBestMove is initialized
		currentBestMove.Value=-2;
		  numCurrentBestMove=0;
		  for(int i=0;i<MoveList.size();i++){
			  if(((Move)MoveList.elementAt(i)).Value>currentBestMove.Value){
				  currentBestMove=(Move)MoveList.elementAt(i);
				  numCurrentBestMove=1;
			  }
			  else if(((Move)MoveList.elementAt(i)).Value==currentBestMove.Value){
				  numCurrentBestMove++;
				  if(numberGen.nextFloat()<(1.0/numCurrentBestMove)){
				    currentBestMove=(Move)MoveList.elementAt(i);
				  }
			  }
				  
		  }
		return currentBestMove;
	}
	
	public static void updateZobrist(Move aMove){
	
		
		//zobrist states are more compact and represented differently than the board in State or the pieces in Move.
		//They will match up well once bit-boards are implmented in State and pieces are assigned an integer value in the
		//Move class.
		int pieceMoved=aMove.getPieceMovedAsInt();
		int pieceCaptured=aMove.getPieceCapturedAsInt();
		int squareTo=aMove.getToSquareAsBitBoardInt();
		int squareFrom=aMove.getFromSquareAsBitBoardInt();
		
		  //h' = h xor z(source) xor  z(source') xor z(dest) xor  z(dest') xor z(side) xor  z(side')
		currentZobrist=currentZobrist ^ transTable.zobristTable[squareFrom][pieceMoved] ^ transTable.zobristTable[squareFrom][0] 
		               ^ transTable.zobristTable[squareTo][pieceCaptured]^transTable.zobristTable[squareTo][pieceMoved]
		               ^transTable.zobristWhiteOnMove;
	}
	
	public static void undoUpdateZobrist(Move aMove){
//		zobrist states are more compact and represented differently than the board in State or the pieces in Move.
		//They will match up well once bit-boards are implmented in State and pieces are assigned an integer value in the
		//Move class.
		int pieceMoved=aMove.getPieceMovedAsInt();
		int pieceCaptured=aMove.getPieceCapturedAsInt();
		int squareTo=aMove.getToSquareAsBitBoardInt();
		int squareFrom=aMove.getFromSquareAsBitBoardInt();
		
		  //h' = h xor z(source) xor  z(source') xor z(dest) xor  z(dest') xor z(side) xor  z(side')
		currentZobrist=currentZobrist ^ transTable.zobristTable[squareFrom][pieceMoved] ^ transTable.zobristTable[squareFrom][0] 
		               ^ transTable.zobristTable[squareTo][pieceCaptured]^transTable.zobristTable[squareTo][pieceMoved]
		               ^transTable.zobristWhiteOnMove;
	}
	
	public static Move negaMaxDecision(Vector MoveList,State boardState,float Alpha, float Beta) throws Exception{
		Move aMove;
		Collections.sort(MoveList);
		//for all moves in the move list
		for(int i=0;i<MoveList.size();i++){
			
			if(new Date().getTime()>(turnStart.getTime()+1000)){
				throw new Exception("Error in negaMaxDecision: Timer Expired before completion");
			}
			aMove=(Move)MoveList.elementAt(i);
			updateZobrist(aMove);
			
			
			boardState.makeMove(aMove);//make a move
//			need the negaMaxValue of the boardState after the move is made
			  Alpha=-negaMaxValue(boardState,2,-Beta,-Alpha);
			  if((Alpha>=Beta)&&(Beta>=-1)){
				  aMove.Value=Beta;
			  }
			  else {
				  aMove.Value=Alpha;
			  }
			  //May 27 replaced Math.max with the if statement above hoping for better performance
			  //Alpha=Math.max(Alpha,aMove.Value);
			  
			  undoUpdateZobrist(aMove);
			  nodeCount++;
			  
			boardState.undoMove(aMove);//undo the move
		}
		return selectBestMoveRandom(MoveList); 
	}
	
	//java passes in a pointer to boardState, java is always pass by value.
	//this should be called with plyNumber >= 2.
	public static float negaMaxValue(State boardState,int plyNumber, float Alpha, float Beta)throws Exception{
		float value= -20000;
		//float tempNegaMaxValue=-2;
		Move aMove;
		if(boardState.gameOver){
			nodeCount++;
			return -1;
		}
		currentZobrist=transTable.zobristWhiteOnMove^currentZobrist;
		tempTTableEntry=transTable.findEntry(currentZobrist);
		if((tempTTableEntry!=null)&&(tempTTableEntry.depth>=plyDepth)&&(currentZobrist==tempTTableEntry.zobristHash)
				&&(
					((tempTTableEntry.a<tempTTableEntry.value)&&(tempTTableEntry.value<tempTTableEntry.b))
					||((tempTTableEntry.a>=Alpha)&&(Beta<=tempTTableEntry.b))
				)	
			){
			tableMatches++;
			nodeCount++;
			currentZobrist=transTable.zobristWhiteOnMove^currentZobrist;
			return tempTTableEntry.value;
		}
		Vector MoveList=boardState.moveGen();
		Collections.sort(MoveList);
		for(int i=0;i<MoveList.size();i++){
		  if(new Date().getTime()>(turnStart.getTime()+1000)){
			throw new Exception("negaMaxValue: Timer expired before completion of next Ply.");
		  }
		  aMove=(Move)MoveList.elementAt(i);
		  if(plyNumber==plyDepth) {
			  //nodeCount++;
			  //return boardState.calcBoardValue(aMove);
			  value=Math.max(Alpha, boardState.calcBoardValue(aMove));
			  boardState.makeMove(aMove);
		  }
		  else{
		    boardState.makeMove(aMove);
		    //May 27 replaced tempNegaMaxValue with Alpha so less comparisons needed
		    //after this line since using Math.max did not degrade performance as thought.
		    value=Math.max(Alpha,-negaMaxValue(boardState,plyNumber+1,-Beta,-Alpha));
		    //tempNegaMaxValue = -negaMaxValue(boardState,numPlys-1,-Beta,-Alpha);
		  }
		  updateZobrist(aMove);
		//  if(nodeCount==10000){
		//	System.out.println("Current Zobrist during NegaMax is:"+currentZobrist);
		//  }
		  boardState.undoMove(aMove);
		  //if((value>=Beta)&&(Beta>=-1)){
			if(value>Alpha){
			  tempTTableEntry=new TTableEntry(Alpha,Beta,value,currentZobrist,plyNumber);
			  transTable.storeEntry(tempTTableEntry);
			  undoUpdateZobrist(aMove);
			  nodeCount++;
			  Alpha=value;
			  if(Alpha>=Beta){
				  return Alpha;
			  }
		  }
		  undoUpdateZobrist(aMove);
		}
		
		//if(tempNegaMaxValue==-2){
		//	throw new Exception("tempNegaMaxValue was -2 and unchanged. Expected change and value in range -1 to 1");
		//}
		
		tempTTableEntry=new TTableEntry(Alpha,Beta,value,currentZobrist,plyNumber);
		transTable.storeEntry(tempTTableEntry);
		
		nodeCount++;
		//if(value==-1){
		//	System.out.println("negative one");
		//}
		return value;	
	}
	
	public static Move readStdInMove(State boardState){
		//reads a move in the form a1-b2 where letters represent columns
		//and numbers represent rows.
		byte buffer[]=new byte[7];
		int fromX, fromY, newX,newY;
		Move aMove=new Move();
		fromX=-1;
		fromY=-1;
		newX=-1;
		newY=-1;
		
		try{
			System.in.read(buffer);
			switch ((char)buffer[0]) {
			  case 'a': fromX=0;break;
			  case 'b': fromX=1;break;
			  case 'c': fromX=2;break;
			  case 'd': fromX=3;break;
			  case 'e': fromX=4;break;
			}
			switch((char)buffer[1]){
			  case '6': fromY=0;break;
			  case '5': fromY=1;break;
			  case '4': fromY=2;break;
			  case '3': fromY=3;break;
			  case '2': fromY=4;break;
			  case '1':	fromY=5;break;
			}
			switch ((char)buffer[3]) {
			  case 'a': newX=0;break;
			  case 'b': newX=1;break;
			  case 'c': newX=2;break;
			  case 'd': newX=3;break;
			  case 'e': newX=4;break;
			}
			switch((char)buffer[4]){
			  case '6': newY=0;break;
			  case '5': newY=1;break;
			  case '4': newY=2;break;
			  case '3': newY=3;break;
			  case '2': newY=4;break;
			  case '1':	newY=5;break;  
			}
			aMove=new Move();
			aMove.toSquare.SetSquare(newX, newY);
			aMove.fromSquare.SetSquare(fromX,fromY);
		}
		catch(Exception err){
		    err.printStackTrace();
		}
		return aMove;
		
	}
	
}
